/*
* edlib.c - Registry edit interactive fuctions.
*
* Point of this is so that interactive registry editor
* can be accessed from several other programs
*
* 2013-aug: Some minor bugfixes and adjustments
*           Thanks to David Collett for catching and fixing bug in REG_MULTI_SZ editing.
* 2010-jun: New function from  Aleksander Wojdyga: dpi, decode product ID
*           Mostly used on \Microsoft\Windows NT\CurrentVersion\DigitalProductId
*           Now as command in registry editor, but may be moved to chnpw menu later.
* 2010-apr: Lots of bugfix and other patches from
*           Frediano Ziglio <freddy77@gmail.com>
*           His short patch comments:
*           remove leak
*           fix default value, bin and quote
*           support wide char in key
*           support wide character into value names
*           fix export for string with embedded end lines
*           remove some warnings
*           compute checksum writing
*
* 2008-mar: First version. Moved from chntpw.c
* See HISTORY.txt for more detailed info on history.
*
*****
*
* Copyright (c) 1997-2014 Petter Nordahl-Hagen.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* See file GPL.txt for the full license.
*
*****
*/


#include "stdafx.h"
/*
#include <stdio.h>

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "edlib.h"
#include "ntreg.h"
*/

const char edlib_version[] = "edlib version 0.1 140201, (c) Petter N Hagen";

#define ALLOC_DEBUG 0     /* Reg allocation debug hooks */
#define ADDBIN_DEBUG 0     /* Reg expansion debug hooks */

//extern char *val_types[REG_MAX + 1];

struct cmds {
	char cmd_str[12];
	int  cmd_num;
};

#define MCMD_CD 1
#define MCMD_LS 2
#define MCMD_QUIT 3
#define MCMD_CAT  4
#define MCMD_STRUCT 5
#define MCMD_DEBUG 6
#define MCMD_HELP 7
#define MCMD_EXPORTKEY 8
#define MCMD_HIVE 9
#define MCMD_EDIT 10
#define MCMD_ALLOC 11
#define MCMD_FREE 12
#define MCMD_ADDV 13
#define MCMD_DELV 14
#define MCMD_DELVALL 15
#define MCMD_NEWKEY 16
#define MCMD_DELKEY 17
#define MCMD_CATHEX 18
#define MCMD_RDEL 19
#define MCMD_CK 20
#define MCMD_CAT_DPI 21
#define MCMD_ADDBIN 22

struct cmds maincmds[] = {
		{ "cd", MCMD_CD },
		{ "ls", MCMD_LS },
		{ "dir", MCMD_LS },
		{ "q", MCMD_QUIT },
		{ "cat", MCMD_CAT },
		{ "type", MCMD_CAT },
		{ "st", MCMD_STRUCT },
		{ "debug", MCMD_DEBUG },
		{ "hive", MCMD_HIVE },
		{ "ed", MCMD_EDIT },
#if ALLOC_DEBUG
		{ "alloc", MCMD_ALLOC },
		{ "free", MCMD_FREE },
#endif
#if ADDBIN_DEBUG
		{ "addbin", MCMD_ADDBIN },
#endif
		{ "nv", MCMD_ADDV },
		{ "dv", MCMD_DELV },
		{ "delallv", MCMD_DELVALL },
		{ "nk", MCMD_NEWKEY },
		{ "dk", MCMD_DELKEY },
		{ "hex", MCMD_CATHEX },
		{ "rdel", MCMD_RDEL },
		{ "ek", MCMD_EXPORTKEY },
		{ "ck", MCMD_CK },
		{ "?", MCMD_HELP },
		{ "dpi", MCMD_CAT_DPI },
		{ "", 0 }
};

const char *val_types2[REG_MAX + 1] = {
	"REG_NONE", "REG_SZ", "REG_EXPAND_SZ", "REG_BINARY", "REG_DWORD",       /* 0 - 4 */
	"REG_DWORD_BIG_ENDIAN", "REG_LINK",                                     /* 5 - 6 */
	"REG_MULTI_SZ", "REG_RESOUCE_LIST", "REG_FULL_RES_DESC", "REG_RES_REQ", /* 7 - 10 */
	"REG_QWORD",                                                            /* 11     */
};


/* display decoded DigitalProductId
* nkofs = node
* path = "DigitalProductId" or some other
*/
void cat_dpi(struct hive *hdesc, int nkofs, char *path)
{
	/*
	void *data;
	int len, i, type;

	type = get_val_type(hdesc, nkofs, path, 0);
	if (type == -1) {
		printf("cat_dpi: No such value <%s>\n", path);
		return;
	}

	len = get_val_len(hdesc, nkofs, path, 0);
	if (len < 67) {
		printf("cat_dpi: Value <%s> is too short for decoding\n", path);
		return;
	}

	data = (void *)get_val_data(hdesc, nkofs, path, 0, 0);
	if (!data) {
		printf("cat_dpi: Value <%s> references NULL-pointer (bad boy!)\n", path);
		abort();
		return;
	}

	if (type != REG_BINARY) {
		printf("Only binary values\n");
		return;
	}

	printf("Value <%s> of type %s, data length %d [0x%x]\n", path,
		(type < REG_MAX ? val_types[type] : "(unknown)"), len, len);


	char digits[] = { 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y', '2', '3', '4', '6', '7', '8', '9' };

#define RESULT_LEN 26
	char result[RESULT_LEN];
	memset(result, 0, RESULT_LEN);

#define START_OFFSET 52
#define BUF_LEN 15
	unsigned char buf[BUF_LEN];
	memcpy(buf, (void *) (data + START_OFFSET), BUF_LEN);

	for (i = RESULT_LEN - 2; i >= 0; i--) {
		unsigned int x = 0;

		int j;
		for (j = BUF_LEN - 1; j >= 0; j--) {
			x = (x << 8) + buf[j];
			buf[j] = x / 24;
			x = x % 24;
		}
		result[i] = digits[x];
	}

	printf("\nDecoded product ID: [%s]\n", result);
	*/
}

/* display (cat) the value,
* vofs = offset to 'nk' node, paths relative to this (or 0 for root)
* path = path string to value
* Does not handle all types yet (does a hexdump instead)
*/
void cat_vk(struct hive *hdesc, int nkofs, char *path, int dohex)
{
	void *data;
	int len, i, type;
	//  char string[SZ_MAX+1];
	char *string = NULL;
	struct keyval *kv = NULL;

	type = get_val_type(hdesc, nkofs, path, TPF_VK);
	if (type == -1) {
		printf("cat_vk: No such value <%s>\n", path);
		return;
	}

	len = get_val_len(hdesc, nkofs, path, TPF_VK);
	if (!len) {
		printf("cat_vk: Value <%s> has zero length\n", path);
		return;
	}

#if 0
	data = (void *)get_val_data(hdesc, nkofs, path, 0, TPF_VK);
	if (!data) {
		printf("cat_vk: Value <%s> references NULL-pointer (bad boy!)\n", path);
		abort();
		return;
	}
#endif

	kv = get_val2buf(hdesc, NULL, nkofs, path, 0, TPF_VK);

	if (!kv) {
		printf("cat_vk: Value <%s> could not fetch data\n", path);
		abort();
	}
	data = (void *)&(kv->data);


	printf("Value <%s> of type %s (%x), data length %d [0x%x]\n", path,
		(type < REG_MAX ? val_types2[type] : "(unknown)"), type, len, len);

	if (dohex) type = REG_BINARY;
	switch (type) {
	case REG_SZ:
	case REG_EXPAND_SZ:
	case REG_MULTI_SZ:
		string = string_regw2prog(data, len);
		//    cheap_uni2ascii(data,string,len);
		for (i = 0; i < (len >> 1) - 1; i++) {
			if (string[i] == 0) string[i] = '\n';
			if (type == REG_SZ) break;
		}
		puts(string);
		FREE(string);
		break;
	case REG_DWORD:
		printf("0x%08x", *(unsigned short *)data);
		break;
	default:
		printf("Don't know how to handle type yet!\n");
	case REG_BINARY:
		hexdump((char *)data, 0, len, 1);
	}
	putchar('\n');
	FREE(kv);

}

/* Edit value: Invoke whatever is needed to edit it
* based on its type
*/

void edit_val(struct hive *h, int nkofs, char *path)
{
	struct keyval *kv, *newkv;
	int type, len, n, i, in, go, newsize, d = 0, done, insert = 0;
	char inbuf[SZ_MAX + 4];
	char origstring[SZ_MAX + 4];
	char *newstring;
	char *dbuf;

	type = get_val_type(h, nkofs, path, TPF_VK);
	if (type == -1) {
		printf("Value <%s> not found!\n", path);
		return;
	}

	kv = get_val2buf(h, NULL, nkofs, path, type, TPF_VK);
	if (!kv) {
		printf("Unable to get data of value <%s>\n", path);
		return;
	}
	len = kv->len;

	printf("EDIT: <%s> of type %s (%x) with length %d [0x%x]\n", path,
		(type < REG_MAX ? val_types2[type] : "(unknown)"), type,
		len, len);

	switch (type) {
	case REG_DWORD:
		printf("DWORD: Old value %d [0x%x], ", kv->data, kv->data);
		fmyinput("enter new value (prepend 0x if hex, empty to keep old value)\n-> ",
			inbuf, 12);
		if (*inbuf) {
			sscanf(inbuf, "%i", &kv->data);
			d = 1;
		}
		printf("DWORD: New value %d [0x%x], ", kv->data, kv->data);
		break;
	case REG_SZ:
	case REG_EXPAND_SZ:
	case REG_MULTI_SZ:
		newstring = NULL;
		dbuf = (char *)&kv->data;
		cheap_uni2ascii(dbuf, origstring, len);
		n = 0; i = 0;
		while (i < (len >> 1) - 1) {
			printf("[%2d]: %s\n", n, origstring + i);
			i += strlen(origstring + i) + 1;
			n++;
		}

		printf("\nNow enter new strings, one by one.\n");
		printf("Enter nothing to keep old.\n");
		if (type == REG_MULTI_SZ) {
			printf("'--n' to quit (remove rest of strings)\n");
			printf("'--i' insert new string at this point\n");
			printf("'--q' to quit (leaving remaining strings as is)\n");
			printf("'--Q' to quit and discard all changes\n");
			printf("'--e' for empty string in this position\n");
		}
		n = 0; i = 0; in = 0; go = 0; done = 0;

		/* Now this one is RATHER UGLY :-} */

		while (i < (len >> 1) - 1 || !done) {

			printf("[%2d]: %s\n", n, insert == 1 ? "[INSERT]" : ((i < (len >> 1) - 1) ? origstring + i : "[NEW]"));
			if (insert) insert++;
			if (!go) fmyinput("-> ", inbuf, 500);
			else *inbuf = 0;
			if (*inbuf && strcmp("--q", inbuf)) {
				if (!strcmp("--n", inbuf) || !strcmp("--Q", inbuf)) { /* Zap rest */
					i = (len >> 1); done = 1;
				}
				else if (strcmp("--i", inbuf)) {  /* Copy out given string */
					if (!strcmp("--e", inbuf)) *inbuf = '\0';
					if (newstring) newstring = (char*) realloc(newstring, in + strlen(inbuf) + 1);
					else newstring = (char*)malloc(in + strlen(inbuf) + 1);
					strcpy(newstring + in, inbuf);
					in += strlen(inbuf) + 1;
				}
				else {
					insert = 1;
				}
			}
			else {  /* Copy out default string */

				if (newstring) newstring = (char*)realloc(newstring, in + strlen(origstring + i) + 1);
				else newstring = (char*)malloc(in + strlen(origstring + i) + 1);
				strcpy(newstring + in, origstring + i);
				in += strlen(origstring + i) + 1;

				if (!strcmp("--q", inbuf)) {
					go = 1; done = 1;
					if (!(i < (len >> 1) - 1)) {
						in--;  /* remove last empty if in NEW-mode */
					}
				}
			}

			if (!insert) i += strlen(origstring + i) + 1;
			if (insert != 1) n++;
			if (insert == 2) insert = 0;
			if (type != REG_MULTI_SZ) {
				i = (len << 1);
				done = 1;
			}

		}

		if (strcmp("--Q", inbuf)) {  /* We didn't bail out */
			if (newstring) newstring = (char*)realloc(newstring, in + 1);
			else newstring = (char*)malloc(in + 1);


			if (type == REG_MULTI_SZ) {
				*(newstring + in) = '\0';  /* Must add null termination */
				in++;
			}

			ALLOCKEYVAL(newkv, 1, (in << 1) + sizeof(int));

			newkv->len = in << 1;
			VERBF(h, "newkv->len: %d\n", newkv->len);
			cheap_ascii2uni(newstring, (char *)&(newkv->data), in);

			d = 1;

			FREE(kv);
			kv = newkv;

		}
		break;

	default:
		printf("Type not handeled (yet), invoking hex editor on data!\n");
	case REG_BINARY:
		fmyinput("New length (ENTER to keep same): ", inbuf, 90);
		if (*inbuf) {
			newsize = atoi(inbuf);
			ALLOCKEYVAL(newkv, 1, newsize + sizeof(int) + 4);
			bzero(newkv, newsize + sizeof(int) + 4);
			memcpy(newkv, kv, ((len < newsize) ? (len) : (newsize)) + sizeof(int));
			FREE(kv);
			kv = newkv;
			kv->len = newsize;
		}
		d = debugit((char *)&kv->data, kv->len);
		break;
	}

	if (d) {
		if (!(put_buf2val(h, kv, nkofs, path, type, TPF_VK))) {
			printf("Failed to set value!?\n");
		}
	}
	FREE(kv);
}

/* look up command in array
*/
int parsecmd(char **s, struct cmds *cmd)
{

	int l = 0;

	while ((*s)[l] && ((*s)[l] != ' ')) {
		l++;
	}
	while (cmd->cmd_num) {
		if (!strncmp(*s, cmd->cmd_str, l)) {
			*s += l;
			return(cmd->cmd_num);
		}
		cmd++;
	}
	return(0);
}


/* Lot of people didn't understand the "nv" command.
* Actually the command should understand the type names too, but.. some later time
*/

void nv_help(void)
{
	int i;

	printf("Command syntax is:\n\n"
		" nv <type> <valuename>\n\n"
		"where <type> should be the HEX NUMBER from one of these registry value types:\n\n");

	for (i = 0; i < REG_MAX; i++) {
		printf(" %2x : %s\n", i, val_types2[i]);
	}
	printf("\nExample:\n nv 4 foobar\n");
	printf("to make a new value named foobar of the type REG_DWORD\n\n");
}


/* Interactive registry editor
* hive - list of loaded hives (array pointing to hive structs)
* no_hives - max number of hives loaded
*/

void regedit_interactive(struct hive *hive)
{
	struct hive *hdesc;
	int cdofs, newofs;
	//  struct nk_key *cdkey;
	char inbuf[100], *bp, *file, *prefix;
	char path[1000];
	int l, vkofs, nh, i;
	int usehive = 0;
	struct keyval *kv;
	int no_hives = 1;

#if ALLOC_DEBUG
	int pagestart;
	int freetest;
#endif

	hdesc = hive;
	cdofs = hdesc->rootofs;

	printf("Simple registry editor. ? for help.\n");

	while (1) {
		// cdkey = (struct nk_key *)(hdesc->buffer + cdofs);

		*path = 0;
		get_abs_path(hdesc, cdofs + 4, path, 50);

#if ALLOC_DEBUG
		pagestart = find_page_start(hdesc, cdofs);
		printf("find_page_start: 0x%x\n", pagestart);
		freetest = find_free_blk(hdesc, pagestart, 10);
		printf("find_free_blk: 0x%x\n", freetest);
#endif
		if (hdesc->state & HMODE_VERBOSE) printf("\n[%0x] %s> ", cdofs, path);
		else printf("\n%s> ", path);
		l = fmyinput("", inbuf, 90);
		bp = inbuf;
		skipspace(&bp);

		if (l > 0 && *bp) {
			switch (parsecmd(&bp, maincmds)) {
			case MCMD_HELP:
				printf("Simple registry editor:\n");
				printf("hive [<n>]             - list loaded hives or switch to hive numer n\n");
				printf("cd <key>               - change current key\n");
				printf("ls | dir [<key>]       - show subkeys & values,\n");
				printf("cat | type <value>     - show key value\n");
				printf("dpi <value>            - show decoded DigitalProductId value\n");
				printf("hex <value>            - hexdump of value data\n");
				printf("ck [<keyname>]         - Show keys class data, if it has any\n");
				printf("nk <keyname>           - add key\n");
				printf("dk <keyname>           - delete key (must be empty)\n");
				printf("ed <value>             - Edit value\n");
				printf("nv <type#> <valuename> - Add value\n");
				printf("dv <valuename>         - Delete value\n");
				printf("delallv                - Delete all values in current key\n");
				printf("rdel <keyname>         - Recursively delete key & subkeys\n");
				printf("ek <filename> <prefix> <keyname>  - export key to <filename> (Windows .reg file format)\n");
				printf("debug                  - enter buffer hexeditor\n");
				printf("st [<hexaddr>]         - debug function: show struct info\n");
				printf("q                      - quit\n");
				break;

			case MCMD_DELKEY:
				bp++;
				skipspace(&bp);
				del_key(hdesc, cdofs + 4, bp);
				break;
			case MCMD_NEWKEY:
				bp++;
				skipspace(&bp);
				add_key(hdesc, cdofs + 4, bp);
				break;
			case MCMD_DELVALL:
				bp++;
				skipspace(&bp);
				del_allvalues(hdesc, cdofs + 4);
				break;
			case MCMD_DELV:
				bp++;
				skipspace(&bp);
				del_value(hdesc, cdofs + 4, bp, 0);
				break;
			case MCMD_ADDV:
				bp++;
				skipspace(&bp);
				if (!isxdigit(*bp)) {
					nv_help();
					break;
				}
				nh = gethex(&bp);
				skipspace(&bp);
				if (!*bp) {
					nv_help();
					break;
				}
				add_value(hdesc, cdofs + 4, bp, nh);
				break;
#if ALLOC_DEBUG
			case MCMD_FREE:
				bp++;
				skipspace(&bp);
				nh = gethex(&bp);
				free_block(hdesc, nh);
				break;
			case MCMD_ALLOC:
				bp++;
				skipspace(&bp);
				nh = gethex(&bp);
				alloc_block(hdesc, cdofs + 4, nh);
				break;
#endif
#if ADDBIN_DEBUG
			case MCMD_ADDBIN:
				bp++;
				skipspace(&bp);
				nh = gethex(&bp);
				add_bin(hdesc, nh);
				break;
#endif
			case MCMD_LS:
				bp++;
				skipspace(&bp);
				nk_ls(hdesc, bp, cdofs + 4, 0);
				break;
			case MCMD_CK:
				bp++;
				skipspace(&bp);
				kv = get_class(hdesc, cdofs + 4, bp);
				if (kv) {
					hexdump((char *)&kv->data, 0, kv->len, 1);
					FREE(kv);
				}
				break;
			case MCMD_RDEL:
				bp++;
				skipspace(&bp);
				rdel_keys(hdesc, bp, cdofs + 4);
				break;
			case MCMD_EDIT:
				bp++;
				skipspace(&bp);
				edit_val(hdesc, cdofs + 4, bp);
				break;
			case MCMD_HIVE:
				/*
				bp++;
				skipspace(&bp);
				if (*bp) {
					nh = gethex(&bp);
					if (nh >= 0 && nh < no_hives) {
						usehive = nh;
						printf("Switching to hive #%d, named <%s>, size %d [0x%x]\n",
							usehive, hive[usehive]->filename,
							hive[usehive]->size,
							hive[usehive]->size);
						hdesc = hive[usehive];
						cdofs = hdesc->rootofs;
					}
				}
				else {
					for (nh = 0; nh < no_hives; nh++) {
						printf("%c %c %2d %9d 0x%08x <%s>\n", (nh == usehive) ? '*' : ' ',
							(hive[nh]->state & HMODE_DIRTY) ? 'D' : ' ',
							nh,
							hive[nh]->size,
							hive[nh]->size, hive[nh]->filename);
					}
				}
				*/
				break;
			case MCMD_CD:
				bp++;
				skipspace(&bp);
				newofs = trav_path(hdesc, cdofs + 4, bp, TPF_NK);
				if (newofs) cdofs = newofs;
				else printf("Key %s not found!\n", bp);
				break;
			case MCMD_CAT:
				bp++;
				skipspace(&bp);
				cat_vk(hdesc, cdofs + 4, bp, 0);
				break;
			case MCMD_CAT_DPI:
				bp++;
				skipspace(&bp);
				cat_dpi(hdesc, cdofs + 4, bp);
				break;
			case MCMD_CATHEX:
				bp++;
				skipspace(&bp);
				cat_vk(hdesc, cdofs + 4, bp, 1);
				break;
			case MCMD_EXPORTKEY:
				bp++;
				skipspace(&bp);
				file = bp;
				i = 0;
				while (*bp != ' ' && (*bp))
				{
					i++;
					bp++;
				}
				file[i] = '\0';
				bp++;
				skipspace(&bp);
				prefix = bp;
				i = 0;
				while (*bp != ' ' && (*bp))
				{
					i++;
					bp++;
				}
				prefix[i] = '\0';
				bp++;
				skipspace(&bp);
				export_key(hdesc, cdofs + 4, bp, file, prefix);
				break;
			case MCMD_STRUCT:
				bp++;
				skipspace(&bp);
				vkofs = cdofs;
				if (*bp) {
					vkofs = gethex(&bp);
				}
				parse_block(hdesc, vkofs, 2);
				break;
			case MCMD_DEBUG:
				if (debugit(hdesc->buffer, hdesc->size)) hdesc->state |= HMODE_DIRTY;
				break;
			case MCMD_QUIT:
				return;
				break;
			default:
				printf("Unknown command: %s, type ? for help\n", bp);
				break;
			}
		}
	}
}
